// New high-performance, lightweight, non-blocking network server framework written in Go.
// Description: A simple TCP server that receives data from DTU devices and saves it to MySQL.
// Author: zengkai001@qq.com, 2025.03.18

// # In project directory
// cd /home/kzeng/pss_dtu
// # Reset environment
// go clean -modcache
// go env -w GOPROXY=https://goproxy.cn,direct

// # Initialize module (if missing)
// go mod init pss_dtu
// # Install dependencies
// go mod tidy
// Install gnet:                    go get -u github.com/panjf2000/gnet/v2
// Install MySQL connect driver:	go get -u github.com/go-sql-driver/mysql
// run: go run main.go
// test: nc <ip_address> 55555


package main

import (
	"database/sql"
	"log"
	"strings"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/panjf2000/gnet/v2"
)

// Save data to MySQL
const SAVE_DB = true 

// MyTCPServer
type MyTCPServer struct {
	gnet.BuiltinEventEngine
	db *sql.DB
}

// OnBoot: handle server start
func (ts *MyTCPServer) OnBoot(eng gnet.Engine) (action gnet.Action) {
	log.Println("TCP Server started on port 55555")
	// log.Printf("SAVE_DB = %v (True: Data save is on, False: Only log print)\n", SAVE_DB)
	if SAVE_DB {
		log.Println("Data will be saved to MySQL database.")
	} else {
		log.Println("Only print log, No save data to MySQL database.")
	}
	return
}

// OnOpen: handle new connection
func (ts *MyTCPServer) OnOpen(c gnet.Conn) (out []byte, action gnet.Action) {
	log.Printf("New connection from %s\n", c.RemoteAddr().String())
	return
}


	// data				= "1233456789012345" #dtu_id
	// temp_001            = round(random.uniform(10.0, 180.0),1)
	// vib_001             = round(random.uniform(0.0, 10.0), 1)
	// cur_001             = random.randint(1, 50)
	// press_01            = round(random.uniform(0.0, 10.0), 1)
	// spd_001             = random.randint(5, 5000)
	// status              = random.randint(0, 1)
	// err_1               = random.randint(0, 1)
	// err_2               = random.randint(0, 1)
	// env_temp_room1      = round(random.uniform(10.0, 45.0),1)
	// env_humidity_room1  = round(random.uniform(10.0, 100.0),1)
	// alarm               = random.randint(0, 1)
	// echo_data = f"{data},{temp_001},{vib_001},{cur_001},{press_01},{spd_001},{status},{err_1},{err_2},{env_temp_room1},{env_humidity_room1},{alarm}".encode()


// OnTraffic: handle data traffic
func (ts *MyTCPServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
	buffer, err := c.Next(-1) // read all data
	if err != nil {
		log.Printf("Error reading data: %v\n", err)
		return
	}

	if len(buffer) == 0 {
		return
	}

	// parse data
	data := strings.TrimSpace(string(buffer))
	log.Printf("--->>>Received data: '%s' from %s\n", data, c.RemoteAddr().String())

	dtuID, dtuDataMap := parseDTUData(data)
	if errMsg, exists := dtuDataMap["error"]; exists {
		log.Printf("Failed to parse data: %s\n", errMsg)
		return
	}
	log.Printf("Parsed data: dtu_id=%s, dtu_data=%v\n", dtuID, dtuDataMap)

	// if SAVE_DB is true, save data to MySQL
	if SAVE_DB {
		err = ts.saveToMySQL(dtuID, dtuDataMap)
		if err != nil {
			log.Printf("Failed to save data: %v\n", err)
		} else {
			log.Printf("--->>>Data saved: dtu_id=%s, dtu_data=%v\n", dtuID, dtuDataMap)
		}
	} else {
		log.Printf("SAVE_DB is false, data not saved: dtu_id=%s, dtu_data=%v\n", dtuID, dtuDataMap)
	}

	return
}

// OnClose: handle connection close
func (ts *MyTCPServer) OnClose(c gnet.Conn, err error) (action gnet.Action) {
	log.Printf("Connection closed: %s\n", c.RemoteAddr().String())
	return
}

// parseDTUData: Parse a comma-separated string
func parseDTUData(rawData string) (string, map[string]string) {
	// Split the raw data by commas

	log.Printf("Raw data: %s\n", rawData)

	parts := strings.Split(rawData, ",")
	if len(parts) < 12 {
		return "UNKNOWN", map[string]string{"error": "Invalid data format"}
	}

	// Extract dtu_id and other fields
	dtuID := parts[0]
	if len(dtuID) > 15 {
		dtuID = dtuID[:15]
	}


	dataMap := map[string]string{
		"temp_001":            parts[1],
		"vib_001":             parts[2],
		"cur_001":             parts[3],
		"press_01":            parts[4],
		"spd_001":             parts[5],
		"status":              parts[6],
		"err_1":               parts[7],
		"err_2":               parts[8],
		"env_temp_room1":      parts[9],
		"env_humidity_room1":  parts[10],
		"alarm":               parts[11],
	}

	return dtuID, dataMap
}

// Save data to MySQL
// Add this with other top-level declarations
var shanghaiLoc = time.FixedZone("CST", 8*60*60) // UTC+8

func (ts *MyTCPServer) saveToMySQL(dtuID string, dtuData map[string]string) error {
	_, err := ts.db.Exec(`INSERT INTO device_data (
		dtu_id, temp_001, vib_001, cur_001, press_01, spd_001, status, err_1, err_2, env_temp_room1, env_humidity_room1, alarm, timestamp
	) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		dtuID,
		dtuData["temp_001"], dtuData["vib_001"], dtuData["cur_001"], dtuData["press_01"],
		dtuData["spd_001"], dtuData["status"], dtuData["err_1"], dtuData["err_2"],
		dtuData["env_temp_room1"], dtuData["env_humidity_room1"], dtuData["alarm"],
		time.Now().In(shanghaiLoc),
	)
	return err
}

func main() {
	// Connect to MySQL
	// In main() function
	db, err := sql.Open("mysql", "root:Yuntengzhiyu@2024@tcp(localhost:33066)/ddb?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai")


	//----------------------------------------------------------------------------------------
	// // echo 'export DB_USER="root"' >> ~/.bashrc
	// // echo 'export DB_PASS="Yuntengzhiyu@2024"' >> ~/.bashrc
	// // echo 'export DB_HOST="localhost:33066"' >> ~/.bashrc
	// // echo 'export DB_NAME="ddb"' >> ~/.bashrc
	// // source ~/.bashrc

    // //// 从环境变量中读取数据库连接信息
    // dbUser := os.Getenv("DB_USER")
    // dbPass := os.Getenv("DB_PASS")
    // dbHost := os.Getenv("DB_HOST")
    // dbName := os.Getenv("DB_NAME")
    // //// 构造数据库连接字符串
    // dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Asia%%2FShanghai", dbUser, dbPass, dbHost, dbName)
    // //// 连接到 MySQL 数据库
    // db, err := sql.Open("mysql", dsn)
	//----------------------------------------------------------------------------------------


	if err != nil {
		log.Fatalf("Failed to connect to MySQL: %v", err)
	}
	defer db.Close()


	// dtu_id, Device IMEI 15-digits string
	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS device_data (
		id INT AUTO_INCREMENT PRIMARY KEY,
		timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
		dtu_id VARCHAR(50), 
		temp_001 FLOAT,
		vib_001 FLOAT,
		cur_001 INT,
		press_01 FLOAT,
		spd_001 INT,
		status INT,
		err_1 INT,
		err_2 INT,
		env_temp_room1 FLOAT,
		env_humidity_room1 FLOAT,
		alarm INT
	)`)
	if err != nil {
		log.Fatalf("Failed to create table: %v", err)
	}
	log.Println("Table 'device_data' is ready.")

	// Start TCP server
	tcpServer := &MyTCPServer{db: db}
	log.Println("Starting TCP server on port 55555...")
	if err := gnet.Run(tcpServer, "tcp://:55555",
		gnet.WithMulticore(true),       // Enable multicore
		gnet.WithReusePort(true),       // Enable port reuse
		gnet.WithTCPKeepAlive(time.Minute),
	); err != nil {
		log.Fatalf("TCP Server failed: %v", err)
	}
}
